﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Client
{
    public class Chromosome : IComparable<Chromosome>
    {
        private double fitness;

        public double initValue;

        private readonly IList<int> table = new List<int>();

        /// <summary>
        /// Zwraca listę genów
        /// </summary>
        public IList<int> Table { get { return table; } }

        #region IComparable<Chromosome> Members

        /// <summary>
        /// Porównuje wartość dopasowania tego chromosomu z innym
        /// </summary>
        /// <param name="other">Inny chromosom</param>
        /// <returns>1 jeżeli wartość jest lepsza, 0 jeżeli taka sama i -1 jeżeli jest gorsza</returns>
        public int CompareTo(Chromosome other)
        {
            if (other == null || fitness > other.fitness) return 1;

            if (fitness == other.fitness) return 0;

            return -1;
        }

        #endregion

        /// <summary>
        /// Tworzy nową instancję klasy Chromosome
        /// </summary>
        /// <param name="genesNumber">Liczba genów w chromosomie</param>
        public Chromosome(int genesNumber, int rand)
        {
            this.initValue = rand;
            for (var i = 0; i < genesNumber; i++)
            {
                table.Add(rand % 2);
                rand = rand / 2;
            }
        }

        /// <summary>
        /// Tworzy kopię istniejącego chromosomu
        /// </summary>
        /// <param name="chromosome">Chromosom do skopiowania</param>
        public Chromosome(Chromosome chromosome)
        {
            this.fitness = chromosome.fitness;

            for (var i = 0; i < chromosome.table.Count; i++)
                table.Add(chromosome.table[i]);
        }

        /// <summary>
        /// Przeprowadza mutację na chromosomie
        /// </summary>
        /// <param name="probability">Prawdopodobieństwo mutacji</param>
        public void Mutate(double probability)
        {
            var random = new Random();
            for (var i = 0; i < table.Count; i++)
            {
                var currRandom = random.NextDouble();

                //jeżeli prawdopodobieństwo jest mniejsze niż wylosowane
                //wartość genu jest zmieniana z 0 na 1 albo z 1 na 0
                if (currRandom <= probability)
                    table[i] = table[i] == 0 ? 1 : 0;
            }
        }

        /// <summary>
        /// Przeprowadza krzyżowanie tego chromosomu z drugim przy odpowiednim prawdopodobieństwie
        /// </summary>
        /// <param name="chromosome">Drugi chromosom</param>
        /// <param name="probability">Prawdopodobieństwo krzyżowania</param>
        public void Crossover(Chromosome chromosome, double probability)
        {
            // znajdujemy długość krótszego chromosomu
            var maxLength = table.Count > chromosome.table.Count ? chromosome.table.Count : table.Count;
            
            var random = new Random();
            for (var i = 0; i < maxLength; i++)
            {
                var currRandom = random.NextDouble(); 

                //jeżeli prawdopodobieństwo jest mniejsze niż wylosowane
                //wartości genów są wymieniane pomiędzy chromosomami
                if (currRandom <= probability)
                {
                    var t = table[i];
                    table[i] = chromosome.table[i];
                    chromosome.table[i] = t;
                }
            }
        }

        /// <summary>
        /// Zwraca tekst opisujący chromosom
        /// </summary>
        /// <returns>Tekst opisujący chromosom</returns>
        public string Print()
        {
            var sb = new StringBuilder();
            sb.Append('[');

            for (var i = 0; i < table.Count; i++)
            {
                sb.Append(table[i].ToString());
                if (i < table.Count - 1) sb.Append(',');
            }

            sb.Append(']');
            return sb.ToString();
        }

        /// <summary>
        /// Wartość dopasowania
        /// </summary>
        public double Fitness
        {
            get { return fitness; }
            set
            {
                if (fitness != value)
                {
                    fitness = value;
                }
            }
        }
    }
}
